﻿using System.Reflection;
using System.Runtime.Serialization;
using System.Linq.Expressions;
using System;
using System.Collections.Generic;

namespace AE.Remoting {
    /// <summary>
    /// Wraps a delegate into a serializable format.
    /// </summary>
    /// <remarks></remarks>
    [Serializable()]
    public class SerializableDelegate {
        private RuntimeTypeHandle p_decType;
        private RuntimeTypeHandle p_type;
        private object[] p_params;
        private RuntimeMethodHandle p_method;
        private Dictionary<string, object> p_Fields = new Dictionary<string, object>();
        private object p_ReturnValue;
        private Exception p_Exception;

        public object ReturnValue {
            get { return p_ReturnValue; }
        }

        public Exception Exception {
            get { return p_Exception; }
        }

        public Dictionary<string, object> Fields {
            get { return p_Fields; }
        }

        private SerializableDelegate() {

        }

        public SerializableDelegate(Delegate func, object[] @params) {
            p_method = func.Method.MethodHandle;
            p_decType = func.Method.DeclaringType.TypeHandle;
            p_type = Type.GetTypeHandle(func);

            if (func.Target != null) {
                foreach (var field in func.Method.DeclaringType.GetSerializableFields()) {
                    p_Fields.Add(field.Name, field.FastGet(func.Target));
                }
            }
            p_params = @params;
        }

        public object ReCreateTarget() {
            if (p_Fields.Count == 0) return null;
            var decType = Type.GetTypeFromHandle(p_decType);
            var target = decType.FastCreate();
            foreach (FieldInfo field in decType.GetSerializableFields()) {
                field.FastSet(target, p_Fields[field.Name]);
            }
            return target;
        }

        public void Execute() {
            try {
                object target = ReCreateTarget();

                p_ReturnValue = p_method.FastInvoke(target, p_params);

                if (target != null) {
                    foreach (var field in target.GetType().GetSerializableFields()) {
                        p_Fields[field.Name] = field.FastGet(target);
                    }
                }

                if (p_ReturnValue != null && !p_ReturnValue.GetType().IsMarkedSerializable()) {
                    p_ReturnValue = Serializable.Serialize(p_ReturnValue);
                }
            } catch (Exception ex) {
                p_Exception = ex;
            }
        }
    }
}